/*
 * tgunzip  -  gzip decompressor example
 *
 * Copyright (c) 2003 by Joergen Ibsen / Jibz
 * All Rights Reserved
 *
 * http://www.ibsensoftware.com/
 *
 * Copyright (c) 2014-2016 by Paul Sokolovsky
 *
 * This software is provided 'as-is', without any express
 * or implied warranty.  In no event will the authors be
 * held liable for any damages arising from the use of
 * this software.
 *
 * Permission is granted to anyone to use this software
 * for any purpose, including commercial applications,
 * and to alter it and redistribute it freely, subject to
 * the following restrictions:
 *
 * 1. The origin of this software must not be
 *    misrepresented; you must not claim that you
 *    wrote the original software. If you use this
 *    software in a product, an acknowledgment in
 *    the product documentation would be appreciated
 *    but is not required.
 *
 * 2. Altered source versions must be plainly marked
 *    as such, and must not be misrepresented as
 *    being the original software.
 *
 * 3. This notice may not be removed or altered from
 *    any source distribution.
 */

#include <stdlib.h>
#include <stdio.h>

#include "uzlib.h"

#define APP_ADDRESS 0
#define APP_SIZE (256 * 1024) // 256 KB
#define BACKUP_ADDRESS  (APP_ADDRESS + APP_SIZE)
#define BACKUP_SIZE (256 * 1024) // 256 KB

/*通过文件模拟flash擦除，最小擦除单位为 4096 bytes*/
int flash_erase(int addr, int size)
{
    static FILE *flash_file = NULL;
    static const char *flash_filename = "flash.bin";
    
    // 打开或创建flash模拟文件
    if (flash_file == NULL) {
        flash_file = fopen(flash_filename, "rb+");
        if (flash_file == NULL) {
            flash_file = fopen(flash_filename, "wb+");
            if (flash_file == NULL) {
                perror("Failed to open flash file");
                return -1;
            }
        }
    }

    // 确保地址和大小是4096的整数倍
    if (addr % 4096 != 0 || size % 4096 != 0) {
        fprintf(stderr, "Flash erase requires 4096-byte alignment\n");
        return -1;
    }

    // 定位到指定地址
    if (fseek(flash_file, addr, SEEK_SET) != 0) {
        perror("Flash seek failed");
        return -1;
    }

    // 擦除操作(实际是写入0xFF)
    unsigned char erase_byte = 0xFF;
    for (int i = 0; i < size; i++) {
        if (fwrite(&erase_byte, 1, 1, flash_file) != 1) {
            perror("Flash erase failed");
            return -1;
        }
    }
    // fclose(flash_file);

    return 0;
}

/*通过文件模拟flash写，最小写入单位为 1 byte*/
int flash_write(int addr, const unsigned char *data, int size)
{
    static FILE *flash_file = NULL;
    
    if (flash_file == NULL) {
        flash_file = fopen("flash.bin", "rb+");
        if (flash_file == NULL) {
            perror("Failed to open flash file");
            return -1;
        }
    }

    if (fseek(flash_file, addr, SEEK_SET) != 0) {
        perror("Flash seek failed");
        return -1;
    }

    if (fwrite(data, 1, size, flash_file) != size) {
        perror("Flash write failed");
        return -1;
    }

    // 立即同步写入文件系统
    if (fflush(flash_file) != 0) {
        perror("Flash sync failed");
        return -1;
    }

    // fclose(flash_file);
    return 0;
}

/*通过文件模拟flash读，最小读取单位为 1 byte*/
int flash_read(int addr, unsigned char *data, int size)
{
    static FILE *flash_file = NULL;

    if (flash_file == NULL) {
        flash_file = fopen("flash.bin", "rb");
        if (flash_file == NULL) {
            perror("Failed to open flash file");
            return -1;
        }
    }

    if (fseek(flash_file, addr, SEEK_SET) != 0) {
        perror("Flash seek failed");
        return -1;
    }

    if (fread(data, 1, size, flash_file) != size) {
        perror("Flash read failed");
        return -1;
    }

    // fclose(flash_file);
    return 0;
}

static void dest_write_cb(struct uzlib_uncomp *d, unsigned char c)
{
    int addr = (int)(d->dest);
    flash_write(addr, &c, 1);
    d->dest ++; // 更新 dest 指针
    
    // *d->dest++ = c;
}

unsigned char read_dest_flash(const unsigned char *dest)
{
    unsigned char c;
    int addr = (int)dest;
    flash_read(addr, &c, 1);
    return c; // 返回从 flash 读取的字节
    // return *dest; // 直接返回 dest 指向的字节
}

/* produce decompressed output in chunks of this size */
/* default is to decompress byte by byte; can be any other length */
#define OUT_CHUNK_SIZE 1

void exit_error(const char *what)
{
   printf("ERROR: %s\n", what);
   exit(1);
}

int main(int argc, char *argv[])
{
    FILE *fin, *fout;
    unsigned int len, dlen, outlen;
    const unsigned char *source;
    unsigned char *dest;
    int res;

    printf("tgunzip - example from the tiny inflate library (www.ibsensoftware.com)\n\n");

    if (argc < 3)
    {
       printf(
          "Syntax: tgunzip <source> <destination>\n\n"
          "Both input and output are kept in memory, so do not use this on huge files.\n");

       return 1;
    }

    uzlib_init();

    /* -- open files -- */

    if ((fin = fopen(argv[1], "rb")) == NULL) exit_error("source file");

    if ((fout = fopen(argv[2], "wb")) == NULL) exit_error("destination file");

    /* -- read source -- */

    fseek(fin, 0, SEEK_END);

    len = ftell(fin);

    fseek(fin, 0, SEEK_SET);

    source = (unsigned char *)malloc(len);

    if (source == NULL) exit_error("memory");

    if (fread((unsigned char*)source, 1, len, fin) != len) exit_error("read");

    fclose(fin);

    if (len < 4) exit_error("file too small");

    /* -- get decompressed length -- */

    dlen =            source[len - 1];
    dlen = 256*dlen + source[len - 2];
    dlen = 256*dlen + source[len - 3];
    dlen = 256*dlen + source[len - 4];

    outlen = dlen;

    /* there can be mismatch between length in the trailer and actual
       data stream; to avoid buffer overruns on overlong streams, reserve
       one extra byte */
    dlen++;

    // dest = (unsigned char *)malloc(dlen);

    // if (dest == NULL) exit_error("memory");
    dest = APP_ADDRESS; // 使用 flash 地址作为 dest

    /* -- decompress data -- */
    flash_erase(APP_ADDRESS, APP_SIZE);
    struct uzlib_uncomp d;
//    uzlib_uncompress_init(&d, malloc(32768), 32768);
    uzlib_uncompress_init(&d, NULL, 0);

    /* all 3 fields below must be initialized by user */
    d.source = source;
    d.source_limit = source + len - 4;
    d.source_read_cb = NULL;
    d.dst_write_cb = dest_write_cb;
    read_dest = read_dest_flash;

    res = uzlib_gzip_parse_header(&d);
    if (res != TINF_OK) {
        printf("Error parsing header: %d\n", res);
        exit(1);
    }

    d.dest_start = d.dest = dest;

    while (dlen) {
        unsigned int chunk_len = dlen < OUT_CHUNK_SIZE ? dlen : OUT_CHUNK_SIZE;
        d.dest_limit = d.dest + chunk_len;
        res = uzlib_uncompress_chksum(&d);
        dlen -= chunk_len;
        if (res != TINF_OK) {
            break;
        }
    }

    if (res != TINF_DONE) {
        printf("Error during decompression: %d\n", res);
        exit(-res);
    }

    printf("decompressed %lu bytes\n", d.dest - dest);

#if 0
    if (d.dest - dest != gz.dlen) {
        printf("Invalid decompressed length: %lu vs %u\n", d.dest - dest, gz.dlen);
    }

    if (tinf_crc32(dest, gz.dlen) != gz.crc32) {
        printf("Invalid decompressed crc32\n");
    }
#endif

    /* -- write output -- */

    // fwrite(dest, 1, outlen, fout);
    for(int i = 0; i < outlen; i++) {
        unsigned char c = read_dest_flash(dest + i);
        fwrite(&c, 1, 1, fout);
    }

    fclose(fout);

    return 0;
}
